using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections;
using System.Diagnostics;
using System.Text;
using System.IO;

using InfFront.UICore;

namespace InfFront.InfoSources.Base
{
    public abstract partial class AbstractListInfoSource : InfoSource
    {
        #region Private Variables
        private IList list = null;
        private int maxEntryCount = -1;
        private LinkedList<WeakReference> bookmarks = null;
        #endregion

        #region Construction/Initialization
        public AbstractListInfoSource()
        {
            Init();
        }

        public AbstractListInfoSource(IContainer container)
        {
            container.Add(this);
            Init();
        }

        private void Init()
        {
            InitializeComponent();
            bookmarks = new LinkedList<WeakReference>();
        }
        #endregion

        #region Public Properties

        [DefaultValue(-1)]
        public int MaxEntryCount
        {
            get { return maxEntryCount; }
            set { maxEntryCount = value; }
        }

        #endregion Public Properties

        public override bool HasEntries()
        {
            var lst = List;
            return (lst != null) && (lst.Count > 0);
        }

        protected abstract IList GetInternalList();
        protected abstract object CreateEmptyItem();

        protected IList List
        {
            get
            {
                if (list == null)
                    list = GetInternalList();
                return list;
            }
        }

        protected LinkedList<WeakReference> ActiveBookmarks
        {
            get { return bookmarks; }
        }

        protected sealed override IInfoEntries GetEntries(int maxCount, EntryFilters filters, EntrySortDescriptions sorts)
        {
            return new Entries(this, maxCount, filters, sorts);
        }

        protected override void BeginRebuild()
        {
            list = null;
            base.BeginRebuild();
        }

        protected internal sealed override object UnwrapEntry(IInfoEntry entry)
        {
            if (entry == null)
                return null;

            var asEntry = (Entry)entry;
            if (asEntry.Owner != this)
                throw new ArgumentException("The IInfoEntry belongs to other InfoSource", "entry");

            if (asEntry.ListIndex == Entry.EMPTY_ENTRY_INDEX)
                return CreateEmptyItem();
            return List[asEntry.ListIndex];
        }

        protected override void FireAfterEntryChange(object obj, IInfoEntry entry, IEnumerable<InfoField> changedFields)
        {
            var asEntry = (Entry)entry;
            if (asEntry.ListIndex == Entry.EMPTY_ENTRY_INDEX)
                asEntry.ListIndex = List.Add(asEntry);
            base.FireAfterEntryChange(obj, entry, changedFields);
        }

        protected sealed override IInfoEntry CreateEmptyEntry()
        {
            return new Entry(this, Entry.EMPTY_ENTRY_INDEX);
        }

        protected override bool SupportsSorting
        {
            get { return true; }
        }


        protected class Entry : IInfoEntry
        {
            private AbstractListInfoSource owner;
            private LinkedListNode<WeakReference> node;
            private int index = -1;

            public static readonly int EMPTY_ENTRY_INDEX = -2;

            public Entry(AbstractListInfoSource owner, int index)
            {
                this.owner = owner;
                this.index = index;
                this.node  = owner.ActiveBookmarks.AddFirst(new WeakReference(this));
            }

            public AbstractListInfoSource Owner
            {
                get { return owner; }
            }

            public int ListIndex
            {
                get { return index; }
                set { index = value; }
            }

            public object Object
            {
                get { return owner.List[index]; }
            }

            public override bool Equals(object obj)
            {
                var asEntry = (Entry)obj;
                return (asEntry.ListIndex == ListIndex) && (asEntry.Owner == Owner);
            }

            public override int GetHashCode()
            {
                return ListIndex;
            }

            #region IDisposable Members

            public void Dispose()
            {
                owner.ActiveBookmarks.Remove(node);
                owner = null;
                node  = null;
                index = -1;
            }

            #endregion
        }

        protected class Enumerator : IInfoSourceEnumerator
        {
            private Entries owner;
            private int curIndex = -1;

            public Enumerator(Entries owner, int position)
            {
                this.owner = owner;
                curIndex = position;
            }

            #region IInfoSourceEnumerator Members

            public int MoveNext(int steps)
            {
                int nextIndex = curIndex + steps;
                if (nextIndex >= owner.Count)
                {
                    nextIndex = owner.Count - 1;
                    steps = nextIndex - curIndex;
                }
                else if (nextIndex < 0)
                {
                    nextIndex = 0;
                    steps = nextIndex - curIndex;
                }
                curIndex = nextIndex;
                return steps;
            }

            #endregion

            #region IEnumerator<IInfoEntry> Members

            public IInfoEntry Current
            {
                get { return owner[curIndex]; }
            }

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
                owner = null;
                curIndex = -1;
            }

            #endregion

            #region IEnumerator Members

            object System.Collections.IEnumerator.Current
            {
                get { return owner[curIndex]; }
            }

            public bool MoveNext()
            {
                return MoveNext(1) > 0;
            }

            public void Reset()
            {
                curIndex = -1;
            }

            #endregion
        }

        protected class Entries : IInfoEntries
        {
            private AbstractListInfoSource owner;
            private List<Entry> list = null;
            private int count = 0;

            public Entries(AbstractListInfoSource owner,
                           int maxCount, EntryFilters filters, EntrySortDescriptions sorts)
            {
                if (owner.List != null)
                    this.count = owner.List.Count;
                this.owner = owner;
                if (maxCount >= 0 && maxCount < count)
                    count = maxCount;
                bool hasFilters = (filters != null && filters.Count > 0);
                bool hasSorts = (sorts != null && sorts.Count > 0);
                if (hasFilters || hasSorts)
                {
                    list = new List<Entry>(count);

                    for (int i = 0, j = 0; j < count; ++i)
                    {
                        if (hasFilters && !filters.Accepts(owner.List[i]))
                            continue;
                        list.Add(new Entry(owner, i));
                        ++j;
                    }
                    if (hasSorts)
                    {
                        Comparison<Entry> cmp = delegate(Entry a, Entry b)
                        {
                            var aobj = a.Object;
                            var bobj = b.Object;
                            return sorts.Compare(aobj, bobj);
                        };
                        list.Sort(cmp);
                    }
                }
            }

            public AbstractListInfoSource Owner
            {
                get { return owner; }
            }

            #region IInfoEntries Members
            public virtual IInfoSourceEnumerator GetEnumerator(IInfoEntry position)
            {
                Entry pEntry = position as Entry;
                if (position != null && pEntry == null || pEntry != null && pEntry.Owner != owner)
                    throw new ArgumentException("The entry belongs to other InfoSource", "position");
                return new Enumerator(this, IndexOf(position));
            }

            IEnumerator<IInfoEntry> IEnumerable<IInfoEntry>.GetEnumerator()
            {
                return GetEnumerator(null);
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator(null);
            }

            [Browsable(false)]
            public virtual int Count
            {
                get { return count; }
            }

            public virtual IInfoEntry this[int index]
            {
                get 
                {
                    if (list != null)
                        return list[index];
                    return new Entry(owner, index); 
                }
            }

            public virtual int IndexOf(IInfoEntry entry)
            {
                if (entry == null)
                    return -1;
                var asEntry = (Entry)entry;
                if (list != null)
                    return list.IndexOf(asEntry);
                return asEntry.ListIndex;
            }

            int IInfoEntries.GetCount()
            {
                return Count;
            }

            IInfoEntry IInfoEntries.GetItem(int index)
            {
                return this[index];
            }

            public virtual Stream GetData(IInfoEntriesGetDataArgs args)
            {
                var begin = args.GetBegin();
                var begEntry = begin as Entry;
                if (begin != null && begEntry == null || begEntry != null && begEntry.Owner != owner)
                    throw new ArgumentException("The entry belongs to other InfoSource", "Begin");
                var end = args.GetEnd();
                var endEntry = end as Entry;
                if (end != null && endEntry == null || endEntry != null && endEntry.Owner != owner)
                    throw new ArgumentException("The entry belongs to other InfoSource", "End");

                int begIndex = (begEntry != null) ? begEntry.ListIndex : 0;
                int endIndex = (endEntry != null) ? endEntry.ListIndex : (owner.List.Count - 1);

                List<InfoField> fields = null;
                var fieldNames = args.GetFieldNames();
                if (fieldNames == null)
                    fields = owner.Fields;
                else
                {
                    fields = new List<InfoField>();
                    foreach (string fname in fieldNames)
                    {
                        InfoField field = owner.Fields[fname];
                        fields.Add(field);
                    }
                }

                var packer = new FieldDataPacker(GetFieldDataTypeTraits(fields));

                // TODO: Dont use the MemoryStream here. Implement effective proxy stream instead.
                var stream = new MemoryStream();
                var writer = new BinaryWriter(stream);
                if (begIndex == Entry.EMPTY_ENTRY_INDEX)
                {
                    Debug.Assert(endIndex == begIndex);
                    var emptyItem = owner.CreateEmptyItem();
                    if (emptyItem != null)
                        packer.Pack(writer, GetFieldDataValues(fields, emptyItem));
                    else
                        packer.Pack(writer, GetDefaultFieldDataValues(fields));
                }
                else
                {
                    for (int i = begIndex; i <= endIndex; ++i)
                        packer.Pack(writer, GetFieldDataValues(fields, owner.List[i]));
                }
                stream.Seek(0, SeekOrigin.Begin);

                return stream;
            }
            #endregion IInfoEntries Members
        }

        public void BeginRefresh(int entryIndex)
        {
            base.BeginRefresh((entryIndex >= 0) ? new Entry(this, entryIndex) : null);
        }

        public void BeginEdit(int entryIndex)
        {
            if (entryIndex < 0 || entryIndex > List.Count)
                throw new ArgumentOutOfRangeException(string.Format("Invalid entry index: {0}", entryIndex));
            base.BeginEdit(new Entry(this, entryIndex));
        }

        #region IInfoSource Members

        protected override int GetMaxEntryCount()
        {
            return MaxEntryCount;
        }

        #endregion
    }
}
